{
  mitm-cache,
  lib,
  pkgs,
  stdenv,
  callPackage,
}:

let
  getPkg = attrPath: lib.getAttrFromPath (lib.splitString "." (toString attrPath)) pkgs;
in
# the derivation to fetch/update deps for
{
  pkg ? getPkg attrPath,
  pname ? null,
  attrPath ? pname,
  # bwrap flags for the update script (this will be put in bash as-is)
  # this is relevant for downstream users
  bwrapFlags ? "--ro-bind \"$PWD\" \"$PWD\"",
  # deps path (relative to the package directory, or absolute)
  data,
  # redirect stdout to stderr to allow the update script to be used with update script combinators
  silent ? true,
  useBwrap ? stdenv.hostPlatform.isLinux,
}@attrs:

let
  data' =
    removeAttrs
      (
        if builtins.isPath data then
          lib.importJSON data
        else if builtins.isString data then
          lib.importJSON "${dirOf pkg.meta.position}/${data}"
        else
          data
      )
      [
        "!comment"
        "!version"
      ];

  parseArtifactUrl =
    url:
    let
      extension = lib.last (lib.splitString "." url);
      splitUrl = lib.splitString "/" url;
      artifactId = builtins.elemAt splitUrl (builtins.length splitUrl - 3);
      baseVer = builtins.elemAt splitUrl (builtins.length splitUrl - 2);
      filename = builtins.elemAt splitUrl (builtins.length splitUrl - 1);
      filenameNoExt = lib.removeSuffix ".${extension}" filename;
      verCls = lib.removePrefix "${artifactId}-" filenameNoExt;
    in
    rec {
      inherit
        artifactId
        baseVer
        filename
        extension
        ;
      isSnapshot = lib.hasSuffix "-SNAPSHOT" baseVer;
      version =
        if isSnapshot && !lib.hasPrefix "SNAPSHOT" verCls then
          builtins.concatStringsSep "-" (lib.take 3 (lib.splitString "-" verCls))
        else
          baseVer;
      classifier = if verCls == version then null else lib.removePrefix "${version}-" verCls;
      # for snapshots
      timestamp = builtins.elemAt (lib.splitString "-" version) 1;
      buildNum = builtins.elemAt (lib.splitString "-" version) 2;
    };

  parseMetadataUrl =
    url:
    let
      xmlBase = lib.removeSuffix "/maven-metadata.xml" url;
      vMeta = lib.hasSuffix "-SNAPSHOT" xmlBase;
      splitBase = lib.splitString "/" xmlBase;
    in
    if vMeta then
      {
        vMeta = true;
        baseVer = builtins.elemAt splitBase (builtins.length splitBase - 1);
        artifactId = builtins.elemAt splitBase (builtins.length splitBase - 2);
      }
    else
      {
        vMeta = false;
        baseVer = null;
        artifactId = builtins.elemAt splitBase (builtins.length splitBase - 1);
      };

  extractHashArtifact =
    afterHash:
    let
      nameVer = builtins.match "([^/]*)/([^/]*)(/SNAPSHOT)?(/.*)?" afterHash;
      artifactId = builtins.elemAt nameVer 0;
      version = builtins.elemAt nameVer 1;
      isSnapshot = builtins.elemAt nameVer 2 != null;
      cls = builtins.elemAt nameVer 3;
    in
    rec {
      inherit artifactId version isSnapshot;
      baseVer =
        if !isSnapshot then
          version
        else
          builtins.head (builtins.match "(.*)-([^-]*)-([^-]*)" version) + "-SNAPSHOT";
      classifier = if cls == null then null else lib.removePrefix "/" cls;
      clsSuf = if classifier == null then "" else "-${classifier}";
    };

  # replace base#name/ver with base/name/ver/name-ver
  decompressNameVer =
    prefix:
    let
      splitHash = lib.splitString "#" (builtins.concatStringsSep "/" prefix);
      inherit (extractHashArtifact (lib.last splitHash))
        artifactId
        baseVer
        version
        clsSuf
        ;
    in
    if builtins.length splitHash == 1 then
      builtins.head splitHash
    else
      builtins.concatStringsSep "/${artifactId}/${baseVer}/" (
        lib.init splitHash ++ [ "${artifactId}-${version}${clsSuf}" ]
      );

  # `visit` all elements in attrs and merge into a set
  # attrs will be passed as parent1, parent1 will be passed as parent2
  visitAttrs =
    parent1: prefix: attrs:
    builtins.foldl' (a: b: a // b) { } (lib.mapAttrsToList (visit parent1 attrs prefix) attrs);

  # convert a compressed deps.json into an uncompressed json used for mitm-cache.fetch
  visit =
    parent2: parent1: prefix: k: v:
    # groupId being present means this is a metadata xml "leaf" and we shouldn't descend further
    if builtins.isAttrs v && !v ? groupId then
      visitAttrs parent1 (prefix ++ [ k ]) v
    else
      let
        url = "${decompressNameVer prefix}.${k}";
      in
      {
        ${url} =
          if builtins.isString v then
            { hash = v; }
          else
            {
              text =
                let
                  xmlBase = lib.removeSuffix "/maven-metadata.xml" url;
                  meta = parseMetadataUrl url // v;
                  inherit (meta)
                    groupId
                    vMeta
                    artifactId
                    baseVer
                    ;

                  fileList = builtins.filter (x: lib.hasPrefix xmlBase x && x != url) (builtins.attrNames finalData);
                  jarPomList = map parseArtifactUrl fileList;

                  sortByVersion = a: b: (builtins.compareVersions a.version b.version) < 0;
                  sortedJarPomList = lib.sort sortByVersion jarPomList;

                  uniqueVersionFiles = map ({ i, x }: x) (
                    builtins.filter (
                      { i, x }: i == 0 || (builtins.elemAt sortedJarPomList (i - 1)).version != x.version
                    ) (lib.imap0 (i: x: { inherit i x; }) sortedJarPomList)
                  );
                  uniqueVersions' = map (x: x.version) uniqueVersionFiles;
                  releaseVersions = map (x: x.version) (builtins.filter (x: !x.isSnapshot) uniqueVersionFiles);
                  latestVer = v.latest or v.release or (lib.last uniqueVersions');
                  releaseVer = v.release or (lib.last releaseVersions);

                  # The very latest version isn't necessarily used by Gradle, so it may not be present in the MITM data.
                  # In order to generate better metadata xml, if the latest version is known but wasn't fetched by Gradle,
                  # add it anyway.
                  uniqueVersions =
                    uniqueVersions'
                    ++ lib.optional (!builtins.elem releaseVer uniqueVersions') releaseVer
                    ++ lib.optional (!builtins.elem latestVer uniqueVersions' && releaseVer != latestVer) latestVer;

                  lastUpdated =
                    v.lastUpdated
                      or (if vMeta then builtins.replaceStrings [ "." ] [ "" ] snapshotTs else "20240101123456");

                  # the following are only used for snapshots
                  snapshotTsAndNum = lib.splitString "-" latestVer;
                  snapshotTs = builtins.elemAt snapshotTsAndNum 1;
                  snapshotNum = lib.last snapshotTsAndNum;

                  indent = x: s: builtins.concatStringsSep "\n" (map (s: x + s) (lib.splitString "\n" s));
                  containsSpecialXmlChars = s: builtins.match ''.*[<>"'&].*'' s != null;
                in
                # make sure all user-provided data is safe
                assert lib.hasInfix "${builtins.replaceStrings [ "." ] [ "/" ] groupId}/${artifactId}" url;
                assert !containsSpecialXmlChars groupId;
                assert !containsSpecialXmlChars lastUpdated;
                if vMeta then
                  ''
                    <?xml version="1.0" encoding="UTF-8"?>
                    <metadata modelVersion="1.1.0">
                      <groupId>${groupId}</groupId>
                      <artifactId>${artifactId}</artifactId>
                      <version>${baseVer}</version>
                      <versioning>
                        <snapshot>
                          <timestamp>${snapshotTs}</timestamp>
                          <buildNumber>${snapshotNum}</buildNumber>
                        </snapshot>
                        <lastUpdated>${lastUpdated}</lastUpdated>
                        <snapshotVersions>
                    ${builtins.concatStringsSep "\n" (
                      map (
                        x:
                        indent "      " ''
                          <snapshotVersion>${
                            lib.optionalString (x.classifier != null) "\n        <classifier>${x.classifier}</classifier>"
                          }
                            <extension>${x.extension}</extension>
                            <value>${x.version}</value>
                            <updated>${builtins.replaceStrings [ "." ] [ "" ] x.timestamp}</updated>
                          </snapshotVersion>''
                      ) sortedJarPomList
                    )}
                        </snapshotVersions>
                      </versioning>
                    </metadata>
                  ''
                else
                  assert !containsSpecialXmlChars latestVer;
                  assert !containsSpecialXmlChars releaseVer;
                  ''
                    <?xml version="1.0" encoding="UTF-8"?>
                    <metadata modelVersion="1.1.0">
                      <groupId>${groupId}</groupId>
                      <artifactId>${artifactId}</artifactId>
                      <versioning>
                        <latest>${latestVer}</latest>
                        <release>${releaseVer}</release>
                        <versions>
                    ${builtins.concatStringsSep "\n" (map (x: "      <version>${x}</version>") uniqueVersions)}
                        </versions>
                        <lastUpdated>${lastUpdated}</lastUpdated>
                      </versioning>
                    </metadata>
                  '';
            };
      };

  finalData = visitAttrs { } [ ] data';
in
mitm-cache.fetch {
  name = "${pkg.pname or pkg.name}-deps";
  data = finalData // {
    "!version" = 1;
  };
  passthru = lib.optionalAttrs (!builtins.isAttrs data) {
    updateScript = callPackage ./update-deps.nix { } {
      inherit
        pkg
        pname
        attrPath
        bwrapFlags
        data
        silent
        useBwrap
        ;
    };
  };
}
